package com.ibeeking.gmc.global.g.rest.service.impl;

import com.ibeeking.gmc.global.g.rest.service.IDatabaseTemplateService;
import com.ibeeking.gmc.global.g.rest.service.IDatasourceConfigService;
import com.ibeeking.gmc.global.g.rest.service.ITenantDataPushService;
import com.ibeeking.found.common.base.BaseDO;
import com.ibeeking.found.common.config.feign.GlobalClient;
import com.ibeeking.found.common.constants.JobExecutorHandlerConstant;
import com.ibeeking.found.common.db.util.DataSourceUtils;
import com.ibeeking.found.common.enums.PublishStatusEnum;
import com.ibeeking.found.common.enums.PushScriptDataEnum;
import com.ibeeking.gmc.global.api.common.param.ScriptPublishParam;
import com.ibeeking.gmc.global.service.common.bo.DatasourceConfigBO;
import com.ibeeking.gmc.global.service.common.bo.ScriptBO;
import com.ibeeking.gmc.global.service.common.bo.ScriptDatabaseConfigBO;
import com.ibeeking.gmc.global.service.common.bo.DatabaseTemplateBO;
import com.ibeeking.gmc.global.service.common.dos.ScriptPublishDO;
import com.ibeeking.gmc.global.service.common.dos.TenantScriptRelationDO;
import com.ibeeking.gmc.global.service.common.param.ScriptPushParam;
import com.ibeeking.gmc.global.service.mapper.ScriptPublishMapper;
import com.ibeeking.gmc.global.service.mapper.TenantScriptRelationMapper;
import com.ibeeking.found.job.model.BaseJobModel;
import com.ibeeking.found.job.service.IJobService;
import com.ibeeking.nematos.constants.enums.LogClassifyEnum;
import com.ibeeking.nematos.core.utils.SpringBeanUtil;
import com.ibeeking.nematos.exception.BusinessException;
import com.ibeeking.nematos.log.utils.LogUtils;
import com.ibeeking.nematos.redis.utils.RedisUtils;
import com.ibeeking.nematos.utils.data.Collections3;
import com.ibeeking.nematos.utils.data.StringUtils;
import com.ibeeking.nematos.utils.result.ResponseResult;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import javax.annotation.Resource;
import java.sql.Connection;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import java.time.LocalDateTime;
import java.util.*;
import java.util.stream.Collectors;

/**
 * @ClassName TenantDataPushServiceImpl
 * @Description
 * @Author ibeeking
 * @Date 2021-03-09 15:37
 **/
@Service
public class TenantDataPushServiceImpl implements ITenantDataPushService {

    private static final String SCRIPT_STATUS_LOCK = "SCRIPT_STATUS_LOCK:";

    private static final int[] executeStatus = {PushScriptDataEnum.UNEXECUTED.getCode(), PushScriptDataEnum.EXECUTE_FAIL.getCode()};

    @Resource
    private IDatasourceConfigService templateDatabaseConfigServiceImpl;
    @Resource
    private IDatabaseTemplateService templateDatabaseServiceImpl;
    @Resource
    private ScriptPublishMapper scriptPublishMapper;
    @Resource
    private TenantScriptRelationMapper tenantScriptRelationMapper;
    @Resource
    private IJobService jobServiceImpl;
    @Resource
    private RedisUtils redisUtils;
    @Resource
    private GlobalClient globalClient;

    @Transactional(rollbackFor = {Exception.class})
    @Override
    public Boolean executeScript(Long userId, ScriptPushParam param) {
        if (null == param.getAll()) {
            param.setAll(0);
        }
        if (null == param.getActualTime()) {
            param.setActualTime(0);
        }
        if (param.getAll() != 1 && Collections3.isEmpty(param.getTenantIds()) && param.getExecuteType() != 2) {
            throw new BusinessException("请选择需要执行的租户");
        }

        //保存租户脚本
        if (1 == param.getExecuteType() || 3 == param.getExecuteType()){
            List<Integer> tenantIds = param.getTenantIds();
            if (1 == param.getAll() && 2 != param.getExecuteType()) {
                ResponseResult<List<Integer>> responseResult = globalClient.queryTenantIds();
                if (null == responseResult || Collections3.isEmpty(responseResult.getData())) {
                    throw new BusinessException("不存在有效租户");
                }
                tenantIds = responseResult.getData();
            }
            List<ScriptPublishParam> scripts = new ArrayList<>();
            ScriptPublishParam scriptPublishParam = new ScriptPublishParam();
            scriptPublishParam.setSqlScript(param.getScript());
            scriptPublishParam.setIndustry(param.getIndustry());
            scriptPublishParam.setApplication(param.getApplication().name());
            scripts.add(scriptPublishParam);
            Boolean flag = getBean().pushScript(param.getIndustry(), param.getModule(), scripts, tenantIds, null);
            if (!flag) {
                throw new BusinessException("脚本保存失败");
            }
            if (1 == param.getActualTime()) {
                for (Integer tenantId : tenantIds) {
                    BaseJobModel baseJobModel = new BaseJobModel();
                    baseJobModel.setType(2);
                    baseJobModel.setBusinessKey(String.valueOf(tenantId));
                    baseJobModel.setExecutorHandler(JobExecutorHandlerConstant.TENANT_DATA_PUSH_JOB_HANDLER);
                    jobServiceImpl.executeOnceJob(baseJobModel);
                }
            }
        }
        if (2 == param.getExecuteType() || 3 == param.getExecuteType()) {
            List<ScriptPublishParam> templates = new ArrayList<>();
            ScriptPublishParam templateScript = new ScriptPublishParam();
            templateScript.setSqlScript(param.getScript());
            templateScript.setIndustry(param.getIndustry());
            templateScript.setApplication(param.getApplication().name());
            templates.add(templateScript);
            getBean().pushScript(param.getIndustry(), param.getModule(), templates, Arrays.asList(0), PushScriptDataEnum.EXECUTE_SUCCESS.getCode());

            List<DatabaseTemplateBO> databaseTemplateBos = templateDatabaseServiceImpl.queryByApplicationNames(param.getIndustry(), Arrays.asList(param.getApplication().name()));
            if (Collections3.isEmpty(databaseTemplateBos)) {
                throw new BusinessException("未配置脚本执行数据库");
            }
            DatabaseTemplateBO databaseTemplateBO = databaseTemplateBos.get(0);
            DatasourceConfigBO configBO = templateDatabaseConfigServiceImpl.queryById(databaseTemplateBO.getDbConfigId());
            if (null == configBO) {
                throw new BusinessException("未配置脚本执行数据源信息");
            }
            Connection conn = DataSourceUtils.getConnection(configBO.getHost(), configBO.getPort(), databaseTemplateBO.getDbName(), configBO.getUserName(), configBO.getPassword());
            if (null == conn) {
                throw new BusinessException("数据源连接失败");
            }
            try {
                // 关闭自动提交
                conn.setAutoCommit(false);
                Statement stmt = conn.createStatement(ResultSet.TYPE_SCROLL_SENSITIVE, ResultSet.CONCUR_READ_ONLY);
                String[] sqls = param.getScript().split(";");
                for (int i = 0; i < sqls.length; i++) {
                    if (StringUtils.isNotBlank(sqls[i])) {
                        stmt.addBatch(sqls[i]);
                        if (0 == (i + 1) % 1000) {
                            stmt.executeBatch();
                            stmt.clearBatch();
                        }
                    }
                }
                if (0 != sqls.length % 1000) {
                    stmt.executeBatch();
                }
                conn.commit();
            } catch (Exception e) {
                LogUtils.error(LogClassifyEnum.ERROR_LOG, "脚本执行失败", e);
                return false;
            } finally {
                try {
                    if (null != conn) {
                        conn.close();
                    }
                } catch (SQLException e) {
                    LogUtils.error(LogClassifyEnum.ERROR_LOG, "关闭数据源连接失败", e);
                }
            }
        }
        return true;
    }

    /*@Override
    public Boolean executeScript(Long userId, ScriptPushParam param) {
        DatasourceConfigBO templateDatabaseConfigBO = templateDatabaseConfigServiceImpl.queryById(param.getDbConfigId());
        if (null == templateDatabaseConfigBO || PublishStatusEnum.DISABLE.getValue().equals(templateDatabaseConfigBO.getPublishStatus())) {
            LogUtils.warn(LogClassifyEnum.ERROR_LOG, "数据源配置无效[{}]", param.getDbConfigId());
            return false;
        }
        Connection conn = DatasourceConfigServiceImpl.getConnection(templateDatabaseConfigBO.getHost(), templateDatabaseConfigBO.getPort(), templateDatabaseConfigBO.getUserName(), templateDatabaseConfigBO.getPassword());
        try {
            // 关闭自动提交
            conn.setAutoCommit(false);
            Statement stmt = conn.createStatement(ResultSet.TYPE_SCROLL_SENSITIVE, ResultSet.CONCUR_READ_ONLY);
            String[] sqls = param.getScript().split(";");
            for (Integer tenantId : param.getTenantIds()) {
                for (int i = 0; i < sqls.length; i++) {
                    stmt.addBatch(sqls[i].replace("${tenantId}", String.valueOf(tenantId)));
                    if (0 == (i + 1) % 1000) {
                        stmt.executeBatch();
                        stmt.clearBatch();
                    }
                }
            }
            if (0 != sqls.length % 1000) {
                stmt.executeBatch();
            }
            conn.commit();
            return true;
        } catch (Exception e) {
            LogUtils.error(LogClassifyEnum.ERROR_LOG, "脚本执行失败", e);
            return false;
        } finally {
            try {
                if (null != conn) {
                    conn.close();
                }
            } catch (SQLException e) {
                LogUtils.error(LogClassifyEnum.ERROR_LOG, "关闭数据源连接失败", e);
            }
        }
    }*/

    @Transactional(rollbackFor = {Exception.class})
    @Override
    public Boolean pushScript(String industry, String module, List<ScriptPublishParam> scripts, List<Integer> tenantIds, Integer status) {
        if (Collections3.isEmpty(scripts) || Collections3.isEmpty(tenantIds)) {
            return true;
        }
        if (null == status) {
            //默认未执行
            status = PushScriptDataEnum.UNEXECUTED.getCode();
        }
        List<String> names = scripts.stream().map(ScriptPublishParam::getApplication).distinct().collect(Collectors.toList());
        List<DatabaseTemplateBO> databaseTemplateBos = templateDatabaseServiceImpl.queryByApplicationNames(industry, names);
        if (Collections3.isEmpty(databaseTemplateBos)) {
            throw new BusinessException("未配置脚本执行数据库");
        }
        if (names.size() != databaseTemplateBos.size()) {
            throw new BusinessException("数据库配置数量错误");
        }
        Map<String, DatabaseTemplateBO> dbMap = databaseTemplateBos.stream().collect(Collectors.toMap(s -> s.getApplication().name(), s -> s, (s, a) -> a));

        List<ScriptPublishDO> saves = new ArrayList<>();
        ScriptPublishDO publishDO = null;
        for (ScriptPublishParam param : scripts) {
            publishDO = new ScriptPublishDO();
            if (null == param.getApplication()) {
                return true;
            }
            if (StringUtils.isBlank(param.getSqlScript())) {
                return true;
            }
            DatabaseTemplateBO databaseTemplateBO = dbMap.get(param.getApplication());
            publishDO.setDbConfigId(databaseTemplateBO.getDbConfigId());
            publishDO.setDbId(databaseTemplateBO.getId());
            publishDO.setIndustry(industry);
            publishDO.setModule(module);
            publishDO.setSqlScript(param.getSqlScript());
            publishDO.setDescription(param.getDescription());
            publishDO.setPublishStatus(PublishStatusEnum.ENABLE.getValue());
            publishDO.setCreateTime(LocalDateTime.now());
            publishDO.setModifyTime(LocalDateTime.now());
            saves.add(publishDO);
        }
        scriptPublishMapper.insertBatchSomeColumn(saves);
        LogUtils.info("===========脚本saves{}==================",saves);
        List<TenantScriptRelationDO> relations = new ArrayList<>();
        TenantScriptRelationDO relationDO = null;
        for (ScriptPublishDO add : saves) {
            for (Integer tenantId : tenantIds) {
                relationDO = new TenantScriptRelationDO();
                relationDO.setId(add.getId());
                relationDO.setTenantId(tenantId);
                relationDO.setTimes(0);
                relationDO.setPublishStatus(status);

                relationDO.setModifyTime(LocalDateTime.now());
                relations.add(relationDO);
            }
        }
        LogUtils.info("===========脚本relations{}==================",relations);
        tenantScriptRelationMapper.insertBatchSomeColumn(relations);
        return true;
    }

    @Override
    public List<ScriptDatabaseConfigBO> queryExecuteSql(Integer tenantId) {
        List<ScriptDatabaseConfigBO> result = new ArrayList<>();
        //根据租户查询是否有脚本需要执行
        List<TenantScriptRelationDO> relationDos = tenantScriptRelationMapper.selectList(new LambdaQueryWrapper<>(TenantScriptRelationDO.class)
                .eq(TenantScriptRelationDO::getTenantId, tenantId)
                .in(TenantScriptRelationDO::getPublishStatus, Arrays.asList(executeStatus))
                .lt(TenantScriptRelationDO::getTimes, 3));
        if (Collections3.isEmpty(relationDos)) {
            return result;
        }

        //查询对应的执行脚本
        List<ScriptPublishDO> scriptPublishDos = scriptPublishMapper.selectList(new LambdaQueryWrapper<>(ScriptPublishDO.class)
                .eq(ScriptPublishDO::getPublishStatus, PublishStatusEnum.ENABLE.getValue())
                .in(BaseDO::getId, relationDos.stream().map(TenantScriptRelationDO::getId).distinct().collect(Collectors.toList())));
        if (Collections3.isEmpty(scriptPublishDos)) {
            return result;
        }
        //查询对应的数据库
        List<DatabaseTemplateBO> databaseTemplateBos = templateDatabaseServiceImpl.queryByIds(scriptPublishDos.stream().map(ScriptPublishDO::getDbId).distinct().collect(Collectors.toList()));
        if (Collections3.isEmpty(scriptPublishDos)) {
            return result;
        }
        Map<Long, DatabaseTemplateBO> dbMap = databaseTemplateBos.stream().collect(Collectors.toMap(DatabaseTemplateBO::getId, s -> s, (s, a) -> a));

        //查询对应的数据库连接
        List<DatasourceConfigBO> configBos = templateDatabaseConfigServiceImpl.selectNameByIds(scriptPublishDos.stream().map(ScriptPublishDO::getDbConfigId).distinct().collect(Collectors.toList()));
        if (Collections3.isEmpty(configBos)) {
            return result;
        }
        Map<Long, DatasourceConfigBO> dbConfigMap = configBos.stream().collect(Collectors.toMap(DatasourceConfigBO::getId, s -> s, (s, a) -> a));

        Map<Long, ScriptDatabaseConfigBO> map = new HashMap<>();
        ScriptDatabaseConfigBO scriptDatabaseConfigBO = null;
        for (ScriptPublishDO scriptPublishDO : scriptPublishDos) {
            DatasourceConfigBO configBO = dbConfigMap.get(scriptPublishDO.getDbConfigId());
            DatabaseTemplateBO databaseTemplateBO = dbMap.get(scriptPublishDO.getDbId());
            if (null != databaseTemplateBO && null != configBO) {
                String[] split = scriptPublishDO.getSqlScript().split(";");
                ScriptBO scriptBO = null;
                for (String script : split) {
                    scriptBO = new ScriptBO();
                    scriptBO.setId(scriptPublishDO.getId());
                    scriptBO.setSqlScript(script);
                    if (map.containsKey(databaseTemplateBO.getId())) {
                        map.get(databaseTemplateBO.getId()).getScripts().add(scriptBO);
                    } else {
                        scriptDatabaseConfigBO = new ScriptDatabaseConfigBO();
                        scriptDatabaseConfigBO.setHost(configBO.getHost());
                        scriptDatabaseConfigBO.setPort(configBO.getPort());
                        scriptDatabaseConfigBO.setUserName(configBO.getUserName());
                        scriptDatabaseConfigBO.setPassword(configBO.getPassword());
                        scriptDatabaseConfigBO.setDbName(databaseTemplateBO.getDbName());
                        List<ScriptBO> add = new ArrayList<>();
                        add.add(scriptBO);
                        scriptDatabaseConfigBO.setScripts(add);
                        map.put(databaseTemplateBO.getId(), scriptDatabaseConfigBO);
                    }
                }
            }
        }
        return map.values().stream().collect(Collectors.toList());
    }

    @Override
    public Boolean updateScriptStatus(Integer tenantId, List<Long> scriptIds, Integer count, Integer status) {
        if (redisUtils.tryLock(SCRIPT_STATUS_LOCK + tenantId)) {
            try {
                return tenantScriptRelationMapper.updateExecuteScript(scriptIds, tenantId, count, status) > 0;
            } finally {
                redisUtils.unlock(SCRIPT_STATUS_LOCK + tenantId);
            }
        }
        return false;


    }

    private static ITenantDataPushService getBean() {
        return SpringBeanUtil.getBean(ITenantDataPushService.class);
    }
}
